package com.pkk.alert.web.config.config;


import static com.pkk.alert.web.config.content.ScheduleContent.CANCEL_SCHEDULE;
import static com.pkk.alert.web.config.content.ScheduleContent.FAIL_SCHEDULE;
import static com.pkk.alert.web.config.content.ScheduleContent.SCHEDULED_FUTURE_MAP;
import static com.pkk.alert.web.config.content.ScheduleContent.SUCCESS_SCHEDULE;

import com.alibaba.fastjson.JSONObject;
import com.pkk.alert.web.config.properties.ScheduleInfo;
import com.pkk.alert.web.config.properties.ScheduleProperties;
import com.pkk.alert.web.config.properties.ScheduleRuntimeVo;
import com.pkk.alert.web.config.properties.ThreadPoolProperties;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.TriggerContext;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronSequenceGenerator;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;

/**
 * 定时任务配置
 *
 * @author peikunkun
 * @version V1.0
 * @date 2020-10-27 18:06
 **/
@Slf4j
@Configuration
@EnableScheduling
@Lazy(value = false)
public class ScheduleConfig implements SchedulingConfigurer, ApplicationContextAware {


  /**
   * 核心线程池数量
   * <p>
   * default : 返回可用处理器的Java虚拟机的数量
   */
  public static Integer THREAD_CORE = 30;
  /**
   * 最大线程数量
   * default : 返回可用处理器的Java虚拟机的数量 * 5
   */
  public static Integer THREAD_MAX = Integer.MAX_VALUE;
  /**
   * 空闲线程(#CHECK_DOMAIN_THREAD_MAX-#CHECK_DOMAIN_THREAD_CORE)的空闲线程保留时间
   */
  public static Integer THREAD_KEEP_ALIVE = 60;

  public static ApplicationContext APPLICATIONCONTEXT;


  private volatile ScheduledTaskRegistrar registrar;


  @Autowired
  private ThreadPoolProperties threadPoolProperties;
  @Autowired
  private ScheduleProperties scheduleProperties;

  /**
   * 回调允许TaskScheduler和具体Task实例对给定的注册ScheduledTaskRegistrar
   *
   * @Param taskRegistrar:注册器进行配置
   * @author peikunkun
   * @date 2020/10/31 0031 下午 1:56
   */
  @Override
  public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
    this.registrar = taskRegistrar;
    taskRegistrar.setScheduler(taskExecutor());

    //执行定时任务
    doSchedule();
  }


  /**
   * 执行定时任务
   *
   * @return void
   * @Param executor
   * @author peikunkun
   * @date 2020/11/3 0003 下午 3:10
   * @since
   */
  private void doSchedule() {
    final Map<String, String> scheduleInfos = scheduleProperties.getInfo();
    log.info("【定时任务】-[任务详情]-当前任务总量:[{}]-有序启动", scheduleInfos.size());

    //验证合法性的定时任务信息
    verifyLegalitySchedule(scheduleInfos);

    //启动定时任务
    startSchedule();
  }

  /**
   * 启动定时任务
   *
   * @return void
   * @Param legalitySchedule:合法的定时任务信息
   * @author peikunkun
   * @date 2020/11/3 0003 下午 2:40
   * @since
   */
  private void startSchedule() {
    this.registrar.addTriggerTask(() -> {
          if ((!SUCCESS_SCHEDULE.isEmpty()) || (!CANCEL_SCHEDULE.isEmpty())) {
            this.refreshTasks();
          }
        }
        , triggerContext -> new PeriodicTrigger(5L, TimeUnit.SECONDS).nextExecutionTime(triggerContext));
  }

  /**
   * 刷新任务信息
   *
   * @return void
   * @Param
   * @author peikunkun
   * @date 2020/11/4 0004 上午 10:30
   * @since
   */
  private void refreshTasks() {
    //取消定时任务
    cancelSchedule();

    //校对定时任务
    proofreadSchedule();
  }

  /**
   * 取消定时任务
   *
   * @return void
   * @Param
   * @author peikunkun
   * @date 2020/11/4 0004 上午 10:49
   * @since
   */
  private void cancelSchedule() {
    CANCEL_SCHEDULE.parallelStream().forEach(c -> {
      try {
        //获取定时任务信息
        final ScheduleRuntimeVo scheduleRuntimeVo = SCHEDULED_FUTURE_MAP.get(c);
        if (null == scheduleRuntimeVo) {
          return;
        }

        //取消定时任务
        scheduleRuntimeVo.getFuture().cancel(false);

        CANCEL_SCHEDULE.remove(c);

        SCHEDULED_FUTURE_MAP.remove(c);
        log.error("【定时任务执行】-[取消定时任务]-取消成功:[{}]->[{}] ", c, JSONObject.toJSONString(scheduleRuntimeVo));
      } catch (Exception e) {
        log.error("【定时任务执行】-[取消定时任务]-取消失败:[{}] ", c, e);
      }
    });
  }

  /**
   * 校对定时任务
   *
   * @return void
   * @Param
   * @author peikunkun
   * @date 2020/11/4 0004 上午 10:49
   * @since
   */
  private void proofreadSchedule() {
    SUCCESS_SCHEDULE.keySet().parallelStream().forEach(p -> {
      try {
        final ScheduleInfo info = SUCCESS_SCHEDULE.get(p);
        String cron = info.getCron();
        if (StringUtils.isBlank(cron) || !CronSequenceGenerator.isValidExpression(cron)) {
          log.error("【定时任务执行】-[刷新定时任务配置]-cron表达式不合法:[{}]->[{}] ", info.getTaskName(), cron);
          return;
        }
        final String taskId = info.getTaskName().concat(".").concat(info.getMethodName());
        //如果配置一致，则不需要重新创建定时任务
        if (SCHEDULED_FUTURE_MAP.containsKey(taskId) &&
            SCHEDULED_FUTURE_MAP.get(taskId).getScheduleInfo().getCron().equals(cron)) {
          return;
        }

        //如果策略执行时间发生了变化，则取消当前策略的任务
        if (SCHEDULED_FUTURE_MAP.containsKey(taskId)) {
          SCHEDULED_FUTURE_MAP.remove(taskId).getFuture().cancel(false);
        }

        ScheduledFuture<?> future = registrar.getScheduler().schedule(getRunnable(info), getTrigger(info));
        SCHEDULED_FUTURE_MAP.put(taskId, ScheduleRuntimeVo.builder().future(future).scheduleInfo(info).build());
        log.info("【定时任务】- [{}]-启动成功", p);
      } catch (Exception e) {
        log.error("【定时任务】-[定时任务启动失败]-启动失败-信息:[{}]", p, e);
      }
    });
  }

  /**
   * 获取运行器执行定时任务
   *
   * @return java.lang.Runnable
   * @Param scheduleInfo
   * @author peikunkun
   * @date 2020/11/3 0003 下午 2:45
   * @since
   */
  private static Runnable getRunnable(ScheduleInfo scheduleInfo) {
    return new Runnable() {
      @Override
      public void run() {
        try {
          final Object bean = APPLICATIONCONTEXT.getBean(scheduleInfo.getTaskName());
          final Method method = bean.getClass().getMethod(scheduleInfo.getMethodName());
          method.invoke(bean);
        } catch (InvocationTargetException e) {
          log.error("【定时任务】-[定时任务启动错误]执行异常-信息:[{}]", JSONObject.toJSONString(scheduleInfo), e);
        } catch (Exception e) {
          log.error("【定时任务】-[定时任务启动错误]-信息:[{}]", JSONObject.toJSONString(scheduleInfo), e);
        }
      }
    };
  }

  /**
   * 获取触发器
   *
   * @return org.springframework.scheduling.Trigger
   * @Param scheduleInfo
   * @author peikunkun
   * @date 2020/11/3 0003 下午 2:45
   * @since
   */
  private static Trigger getTrigger(ScheduleInfo scheduleInfo) {
    return new Trigger() {
      @Override
      public Date nextExecutionTime(TriggerContext triggerContext) {
        return new CronTrigger(scheduleInfo.getCron()).nextExecutionTime(triggerContext);
      }
    };
  }

  /**
   * 获取合法性的定时任务信息
   *
   * @return java.util.Map<java.lang.String, ScheduleInfo>
   * @Param scheduleInfos
   * @author peikunkun
   * @date 2020/11/3 0003 下午 2:32
   * @since
   */
  private void verifyLegalitySchedule(Map<String, String> scheduleInfos) {
    scheduleInfos.keySet().stream().forEach(s -> {
      try {
        final String cron = scheduleInfos.get(s);
        ScheduleInfo scheduleInfo = ScheduleInfo.buildScheduleInfo(s, cron);
        if (!ScheduleInfo.verifySchedule(scheduleInfo)) {
          FAIL_SCHEDULE.put(s, cron);
          return;
        }
        SUCCESS_SCHEDULE.put(s, scheduleInfo);
      } catch (Exception e) {
        log.error("【定时任务】-[验证定时任务合法性]-出现异常:[{}]", s, e);
      }
    });
  }

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    if (APPLICATIONCONTEXT == null) {
      APPLICATIONCONTEXT = applicationContext;
    }
  }

  /**
   * 线程执行器
   *
   * @return java.util.concurrent.Executor
   * @Param
   * @author peikunkun
   * @date 2020/10/31 0031 下午 1:55
   * @since
   */
  @Bean(destroyMethod = "shutdown")
  public ThreadPoolTaskScheduler taskExecutor() {
    return threadPool();
  }

  /**
   * 线程池的操作
   *
   * @return java.util.concurrent.ThreadPoolExecutor
   * @author peikunkun
   * @date 2020/10/31 0031 下午 1:39
   * @since
   */
  public ThreadPoolTaskScheduler threadPool() {

    if (null != threadPoolProperties.getCoreSize() && threadPoolProperties.getCoreSize() >= 0) {
      THREAD_CORE = threadPoolProperties.getCoreSize();
    }

    if (null != threadPoolProperties.getMaxSize() && threadPoolProperties.getMaxSize() >= 0) {
      THREAD_MAX = threadPoolProperties.getMaxSize();
    }

    if (null != threadPoolProperties.getKeepAlive() && threadPoolProperties.getKeepAlive() >= 0) {
      THREAD_KEEP_ALIVE = threadPoolProperties.getKeepAlive();
    }

    ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
    executor.setPoolSize(THREAD_CORE);
    executor.setAwaitTerminationSeconds(THREAD_KEEP_ALIVE);
    executor.setWaitForTasksToCompleteOnShutdown(Boolean.TRUE);
    executor.setThreadNamePrefix("this-excutor-");

    //如果线程还是不够用,那就谁发起的谁执行吧（交给调用者线程执行(主线程)）
    executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());

    log.info("【线程池创建】-核心:[{}],最大:[{}],闲时销毁时(秒):[{}]", THREAD_CORE, THREAD_MAX, THREAD_KEEP_ALIVE);
    return executor;
  }


}
